import Grid from "./Grid";

const {ccclass, property} = cc._decorator;
const GRID_SIZE = 4;
const DURATION = 0.1; //常量设置缓动时间
const MOVE_DISTANCE = 10 //设置能移动的距离
const KEY_SCORE_BEST = 'KEY_SCORE_BEST';//储存最高分数的 key

@ccclass
export default class Game extends cc.Component {
    @property(cc.Node)
    private bgNode:cc.Node = null;
    @property(cc.Node)
    private rootNode:cc.Node = null;

    @property(cc.Label)
    private lblScoreNow: cc.Label = null;
    @property(cc.Label)
    private lblScoreBest:cc.Label = null;

    private grids:Grid[][] = [];
    private gridsReversed:Grid[][] = [];

    private _moving = false;
    private _canTouchMove = false; //属性标识当前是否能触摸移动。
    private _scoreNow :number = 0;
    private _scoreBest:number = 0
    private _panelFailed:cc.Node;

    onLoad () {
        this.init();
    }
    private init() {
        const gridPrefab = this.rootNode.children[0];
        gridPrefab.active = false;

        let idx = 0
        for (let col = 0; col < GRID_SIZE; col++) {
            this.grids[col] = []
            for (let row = 0; row < GRID_SIZE; row++) {
                const grid = cc.instantiate(gridPrefab);
                grid.parent = this.rootNode;
                const gridComp = grid.getComponent(Grid);
                gridComp.init(this.bgNode.children[idx].getPosition());

                // grid.active = true
                // gridComp.num = Math.pow(2, idx + 1)

                this.grids[col][row] = gridComp;
                idx++;
            }
        }

        gridPrefab.destroy()

        for (let col = 0; col < GRID_SIZE; col++) {
            this.gridsReversed[col] = []
            for (let row = 0; row < GRID_SIZE; row++) {
                this.gridsReversed[col][row] = this.grids[row][col]
            }
        }
        this._scoreBest = parseInt(cc.sys.localStorage.getItem(KEY_SCORE_BEST) ?? 0);
        this.showScore();

        const btnRestart = this.node.getChildByName("btnRestart");
        btnRestart.on("click",()=> cc.game.restart());//重新开始

        this.initPanelFailed();//失败的方法
    }

    start () {
        this.randGrid();
        this.randGrid();

        // 触摸事件
        this.node.on(cc.Node.EventType.TOUCH_START,(_et:any)=> this._canTouchMove = true,this);
        this.node.on(cc.Node.EventType.TOUCH_MOVE,this.onTouchMove,this);
        // 键盘事件
        cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN,this.onKeyDown,this);
        // cc.game.canvas.focus();
    }
    onKeyDown(event:cc.Event.EventKeyboard) {
        if(this._moving) return
        if (event.keyCode == cc.macro.KEY.up) {
            this.moveUp();
        }else if (event.keyCode == cc.macro.KEY.down) {
            this.moveDown();
        }else if(event.keyCode == cc.macro.KEY.left){
            this.moveLeft();
        }else if (event.keyCode == cc.macro.KEY.right) {
            this.moveRight();
        }
    }

    // update (dt) {}

    private randGrid() {
        const randGrids: Grid[] = []
        for (const rowGrids of this.grids) {
            for (const grid of rowGrids) {
                if (!grid.num) randGrids.push(grid)
            }
        }
        let randNum:number = this.randRange(0, randGrids.length - 1);
        const grid = randGrids[randNum];
        grid.num = 2;
        grid.node.scale = 0;
        cc.tween(grid.node).to(DURATION, { scale: 1 }).start();
    }
    
    // 整数范围随机 [min, max]
    private randRange(min:number,max:number) :number {
        let num = Math.round(Math.random() * (max - min) + min);
        return num >= 0 ? num : 0;
    }

    private move(grids: Grid[]) {
        let lastIdx = grids.length - 1;
        let lastNum = grids[lastIdx].num;
    
        for (let i = grids.length - 2; i >= 0; i--) {
            const grid = grids[i];
            if (!grid.num) continue
    
            const lastGrid = grids[lastIdx];
            const num = grid.num;
    
            if (!lastNum) {//最后一个格子是空格子，直接往后移动
                this._moving = true;
                lastNum = num;
    
                grid.stopTween();
                cc.tween(grid.node).
                to(DURATION, { x: lastGrid.pos.x, y: lastGrid.pos.y }).
                call(() => {
                    lastGrid.num = num;
                    grid.reset();
                }).start();
            } else if (lastNum == num) {//数字一样合并
                this._moving = true;
                lastIdx -= 1; //合并后不能再次合并，最后一个格子往前移动一个位置并设置为空格子
                lastNum = 0;
    
                grid.stopTween();
                cc.tween(grid.node).
                to(DURATION, { x: lastGrid.pos.x, y: lastGrid.pos.y }).
                call(() => {
                    this.addScore(num * 2);
                    lastGrid.num = num * 2;
                    grid.reset();
                    cc.tween(lastGrid.node).
                    to(DURATION * 0.4, { scale: 1.1 }).
                    delay(DURATION * 0.2).
                    to(DURATION * 0.3, { scale: 1 }).start()
                }).start()
            } else {//下面有数字但不一样
                if (i + 1 == lastIdx) {//不能往下移动
                    lastIdx = i;
                    lastNum = num;
                } else {//往最后格子的上一个格子移动
                    this._moving = true;
                    lastIdx -= 1;
                    lastNum = num;
    
                    const lastPrevGrid = grids[lastIdx];
    
                    grid.stopTween();
                    cc.tween(grid.node).
                    to(DURATION, { x: lastPrevGrid.pos.x, y: lastPrevGrid.pos.y }).
                    call(() => {
                        lastPrevGrid.num = num;
                        grid.reset();
                    }).start();
                }
            }
        }
    }

    private moveUp() {
        this.gridsReversed.forEach(itemsTmp => this.move(itemsTmp.slice().reverse()))
        this.onMoveAfter()
    }
    
    private moveDown() {
        this.gridsReversed.forEach(items => this.move(items))
        this.onMoveAfter()
    }
    
    private moveLeft() {
        this.grids.forEach(itemsTmp => this.move(itemsTmp.slice().reverse()))
        this.onMoveAfter()
    }
    
    private moveRight() {
        this.grids.forEach(items => this.move(items))
        this.onMoveAfter()
    }
    
    private onMoveAfter() {
        if (!this._moving) return
    
        this.scheduleOnce(() => {
            this._moving = false
            this.randGrid();
            this.checkFail();
        }, DURATION)
    }

    onTouchMove(event:cc.Event.EventTouch){
        if (!this._canTouchMove || this._moving) {
            return
        }

        const start = event.getStartLocation();
        const cur = event.getLocation();
        const diff = cur.sub(start);

        if (Math.abs(diff.x) > Math.abs(diff.y)) {
            if (Math.abs(diff.x) < MOVE_DISTANCE) {
                return
            }
            this._canTouchMove = false;
            diff.x > 0 ? this.moveRight() : this.moveLeft();
        }else{
            if (Math.abs(diff.y) < MOVE_DISTANCE) {
                return
            }
            this._canTouchMove = false;
            diff.y > 0 ? this.moveUp() : this.moveDown();
        }
    }

    private addScore(score:number){
        this._scoreNow += score;
        if (this._scoreNow > this._scoreBest) {
            this._scoreBest = this._scoreNow;
            cc.sys.localStorage.setItem(KEY_SCORE_BEST,JSON.stringify(this._scoreBest));
        }
        this.showScore();

    }
    private showScore() {
        this.lblScoreNow.string = this._scoreNow.toString();
        this.lblScoreBest.string = this._scoreBest.toString();
    }

    private initPanelFailed(){
        this._panelFailed = this.node.getChildByName("panelFailed");
        this._panelFailed.active = false;
        this._panelFailed.getChildByName("btnOk").on("click",() => cc.game.restart());
    }

    private checkFail(){
        const gridList = [this.grids,this.gridsReversed];
        for (const grids of gridList) {
            for (const items of grids) {
                for (let index = 1; index < items.length; index++) {
                    if (!items[index].num || !items[index - 1].num || 
                        items[index].num == items[index - 1].num) {
                        return
                    }
                }
            }
        }

        this._moving = true;
        this.scheduleOnce(() => this._panelFailed.active = true,1);

    }

}
